
/*************************************************************************/
/*************************************************************************/
/*************************************************************************/

/*
**
** Copyright (c) 2010 by Lorimark Solutions, LLC.
** All Rights Reserved.
** Send inquiries to: info@lorimarksolutions.com
**
*/

#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/sfr_defs.h>

#include "mega48serial.h"


/*!
** \brief Serial Port Interrupt Service Routines
**
** The following routines take care of handling the 
**  interrupt service procedures for the serial ports.
**  There are two separate circular lists for serial
**  data; Receive and Transmit.
**
** The code between the two RX/TX circular list managers 
**  may look redundant, and that's only because it is.  
**  The reason being is that since these are interrupt 
**  driven routines, the goal is to have them function 
**  as quickly as possible, and having them suffer the 
**  additional lag of relative indexes to relative 
**  buffers is a unreasonably high cost.  So, we have 
**  traded code size for speed.  In other words, these 
**  routines will occupy more code memory than they might, 
**  but they will also run blindingly fast.
**
** Note that in most of the procedures below, interrupts
**  are turned off before the lists are manipulated, and 
**  left off.  This is an important issue, because some of
**  these codes are used in the interrupt service procedures
**  themselves, and we don't want to inadvertently turn
**  interrupts back on when we're simply massaging the
**  circular lists.  Therefore, in your code, when you
**  use these routines, you will have to turn interrupts
**  back on after manipulating the lists.
**
**
** Note that the serial I/O lines are inactive high.  This
**  means no data is moving when the pins are in the high
**  state.
*/

/*
** Set up the buffer structure.
**
*/
struct
{
  uint8 status;
  uint8 current;
  uint8 current_top;
  uint8 next_bottom;
  uint8 items[RX_USART_BUFFER_SIZE];
} rxUsart;

/*
** This resets the reset usart, and also sets up the interrupt
**  conditions.
**
*/
void rxUsartReset(uint8 channel)
{
  channel;
  DISABLE_INTERRUPTS;
  rxUsart.status       =
  rxUsart.current      =
  rxUsart.current_top  =
  rxUsart.next_bottom  = 0;

  //setup interrupts here
}

/*!
** \brief Serial List Item Count
**
** \return Number of items currently in the list
**
*/
//#inline
uint8 rxUsartCount(uint8 channel)
{
  channel;
  return( rxUsart.current );
}

/*!
** \brief Serial List Space Remaining
**
** \return Number of empty slots available in the list
*/
//#inline
uint8 rxUsartSpace(uint8 channel)
{
  channel;
  return( RX_USART_BUFFER_SIZE - rxUsart.current );
}

/*!
** \brief Put an item into the Serial List
**
**
*/
void rxUsartPut( uint8 channel, uint8 data )
{
  channel;

  /*
  ** Cannot allow interrupts to occur while massaging this circular
  **  buffer's registers.
  **
  */
  DISABLE_INTERRUPTS;

  /*
  ** Only add the item to the list if there is room for it
  **
  */
  if( rxUsart.current < RX_USART_BUFFER_SIZE )
  {
    /*
    ** We're adding an item so we must bump the item count
    **
    */
    rxUsart.current++;

    /*
    ** Now we finally know where the item is going so go ahead and
    **  add it.
    **
    */
    rxUsart.items[rxUsart.current_top] = data;

    /*
    ** If the top has reached the bottom of the list, then we must
    **  loop back around to the top, otherwise, just come down
    **  one element in the list.
    **
    */
    if( rxUsart.current_top == 0 )
    {
       rxUsart.current_top = RX_USART_BUFFER_SIZE - 1;
    }
    else
    {
       rxUsart.current_top--;

    } /* endif( list->current_top == 0 ) */

  } /* endif( positions_left > 0 ) */

}

uint8 rxUsartGet(uint8 channel)
{
  channel;
  /*
  ** Return a zero by default
  **
  */
  uint8 retVal = 0xfe;

  /*
  ** Cannot allow interrupts to occur while massaging this circular
  **  buffer's registers.
  **
  */
  DISABLE_INTERRUPTS;

  /*
  ** Only remove the item from the list if it is there
  **
  */
  if( rxUsart.current > 0 )
  {
    /*
    ** We're removing an item so we must bump the item count
    **
    */
    rxUsart.current--;

    /*
    ** Now we finally know where the item is coming from so go ahead and
    **  take it.
    **
    */
    retVal = rxUsart.items[rxUsart.next_bottom];

    /*
    ** If the bottom has reached the bottom of the list, then we must
    **  loop back around to the top, otherwise, just come down
    **  one element in the list.
    **
    */
    if( rxUsart.next_bottom == 0 )
    {
       rxUsart.next_bottom = RX_USART_BUFFER_SIZE - 1;
    }
    else
    {
       rxUsart.next_bottom--;

    } /* endif( list->next_bottom == 0 ) */

  } /* endif( positions_left > 0 ) */

  /*
  ** Return the result of our actions -- remember interrupts are
  **  still turned off.
  **
  */
  return( retVal );

}

ISR(USART_RX_vect)
{
  rxUsartPut( UDR0 );
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


#ifndef TX_USART_BUFFER_SIZE
#error USARTIO.C: You must define TX_USART_BUFFER_SIZE before including this file
#endif

struct
{
  uint8 status;
  uint8 current;
  uint8 current_top;
  uint8 next_bottom;
  uint8 items[TX_USART_BUFFER_SIZE];
} txUsart;

#ifdef TX_USART_BUFFER_ADDRESS
#locate txUsart = TX_USART_BUFFER_ADDRESS
#endif

void txUsartReset(uint8 channel)
{
  channel;
  DISABLE_INTERRUPTS;
  txUsart.status       =
  txUsart.current      =
  txUsart.current_top  =
  txUsart.next_bottom  = 0;
}

uint8 txUsartCount(uint8 channel)
{
  channel;
  return( txUsart.current );
}

uint8 txUsartSpace(uint8 channel)
{
  channel;
  return( TX_USART_BUFFER_SIZE - txUsart.current );
}

void txUsartPut( uint8 channel, uint8 data )
{
  channel;
  DISABLE_INTERRUPTS;

  if( txUsart.current < TX_USART_BUFFER_SIZE )
  {
    txUsart.current++;

    txUsart.items[txUsart.current_top] = data;

    if( txUsart.current_top == 0 )
    {
       txUsart.current_top = TX_USART_BUFFER_SIZE - 1;
    }
    else
    {
       txUsart.current_top--;
    }

    /*
    ** Turn on the interrupts for transmit data ready.
    **  This allows the TX ISP to pick up the data from
    **  the circular list.
    ** xyzzy
    */
    TX_USART_INTERRUPT_ENABLE;
  }

}

uint8 txUsartGet(uint8 channel)
{
  channel;
  uint8 retVal = 0;

  DISABLE_INTERRUPTS;

  if( txUsart.current > 0 )
  {
    txUsart.current--;

    retVal = txUsart.items[txUsart.next_bottom];

    if( txUsart.next_bottom == 0 )
    {
       txUsart.next_bottom = TX_USART_BUFFER_SIZE - 1;
    }
    else
    {
       txUsart.next_bottom--;

    }
  }

  return( retVal );

}

void init_usart(uint8 channel, uint8 baud)
{
  DISABLE_INTERRUPTS;

  /*
  ** Set up the serial port from the documentation (2545M AVR 09/07)
  **
  */
  rxUsartReset(channel);
  txUsartReset(channel);

  /*
  ** USART Control and Status Register A pp191 (2545M AVR 09/07)
  **
  */
  UCSR0A = 
    (0b0   << RXC0)    |  // 7.USART Receive Complete
    (0b0   << TXC0)    |  // 6.USART Transmit Complete
    (0b0   << UDRE0)   |  // 5.USART Data Register Empty
    (0b0   << FE0)     |  // 4.Frame Error
    (0b0   << DOR0)    |  // 3.Data Overrun
    (0b0   << UPE0)    |  // 2.Parity Error
    (0b0   << U2X0)    |  // 1.Double the USART Tranmission Speed
    (0b0   << MPCM0)   ;  // 0.Multi-Processor Communication Mode

  /*
  ** USART Control and Status Register B pp192 (2545M AVR 09/07)
  **
  */
  UCSR0B =
    (0b1   << RXCIE0)  |  // 7.RX Complete Interrupt Enable
    (0b0   << TXCIE0)  |  // 6.TX Complete Interrupt Enable
    (0b0   << UDRIE0)  |  // 5.USART Data Register Empty Interrupt Enable
    (0b1   << RXEN0)   |  // 4.Receiver Enable
    (0b1   << TXEN0)   |  // 3.Transmitter Enable
    (0b0   << UCSZ02)  |  // 2.Character Size
    (0b0   << RXB80)   |  // 1.Receive Data Bit 8
    (0b0   << TXB80)   ;  // 0.Transmit Data Bit 8

  /*
  ** USART Control and Status Register C pp193 (2545M AVR 09/07)
  **
  */
  UCSR0C =
    (0b00  << UMSEL00) | // 6.Mode Select (0=Asynchronous)
    (0b00  << UPM00)   | // 4.Parity Mode (0=none)
    (0     << USBS0)   | // 3.Stop Bit (0=1-bit, 1=2-bit)
    (0b011 << UCSZ00)  ; // 0.Character Size (0b011=8-bit)

  /*
  ** Baud Rate Generator
  **
  ** pp199 table 19-12
  ** 20Mhz, 19200 baud
  ** UBRRn = 64    U2Xn = 0
  ** UBRRn = 129   U2Xn = 1
  **
  ** 20Mhz, 57600 baud
  ** UBRRn = 21    U2Xn = 0
  **
  ** 20Mhz, 115200 baud
  ** UBRRn = 10    U2Xn = 0
  */
  UBRR0 = baud;

}


/*!
** \brief Service interrupt for Transmit Data Buffer Empty
**
** This routine is rediculously simple.  It is responding to the
**  Transmit Buffer Empty interrupt.  Upon interrupt, if there is
**  data waiting in the transmit circular list, it is pulled from
**  the list and sent to the Uart Data Register.  If there is no
**  data then the interrupt mask itself it turned off.
**
*/
ISR(USART_UDRE_vect)
{
  if( txUsart.current > 0 )
  {
    txUsart.current--;

    UDR0 = txUsart.items[txUsart.next_bottom];

    if( txUsart.next_bottom == 0 )
    {
       txUsart.next_bottom = TX_USART_BUFFER_SIZE - 1;
    }
    else
    {
       txUsart.next_bottom--;
    }

  }
  else
  {
    TX_USART_INTERRUPT_DISABLE;
  }
}





